package com.appspot.categoryeditor.controller;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.slim3.controller.Navigation;
import org.slim3.datastore.Datastore;
import org.slim3.util.StringUtil;

import com.appspot.categoryeditor.exception.EntityPropertySizeLimitException;
import com.appspot.categoryeditor.jsonbean.categorygroup.CategoryBean;
import com.appspot.categoryeditor.jsonbean.categorygroup.FolderBean;
import com.appspot.categoryeditor.jsonbean.categorygroup.FoldersBean;
import com.appspot.categoryeditor.meta.CategoryMeta;
import com.appspot.categoryeditor.meta.FolderMeta;
import com.appspot.categoryeditor.meta.UserMeta;
import com.appspot.categoryeditor.model.Category;
import com.appspot.categoryeditor.model.Folder;
import com.appspot.categoryeditor.utils.IOUtils;
import com.appspot.categoryeditor.utils.ModelUtils;
import com.appspot.categoryeditor.utils.UserUtils;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.users.UserServiceFactory;

public class CategoriesController extends AbstractCategoryController {


	private boolean allCategoriesAccessible;

	final static UserMeta USER_META = UserMeta.get();
	final static FolderMeta FOLDER_META = FolderMeta.get();
	final static CategoryMeta CATEGORY_META = CategoryMeta.get();

	@Override
	public Navigation run() throws Exception {
		getParams();
		
		if(StringUtil.isEmpty(userid)){
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return null;
		}
		
		allCategoriesAccessible 
			= UserUtils.checkLogin(request) 
					&& (userid.equals(UserUtils.getLoginUserId())
								|| UserServiceFactory.getUserService().isUserAdmin());
		if (categoryid != null) {
			getCategoryModel();
		}

		if (isGet()) {
			if (categoryid !=null && categoryModel != null) {
				getCategoryProcess();
			} else {
				getCategoriesProcess();
			}
		} else if (isPost() || isPut()) {
			if (categoryModel != null) {
					putCategoryProcess();
			}else{
				putCategoriesProcess();
			}
		} else if(isDelete()){
			deleteCategoryProcess();
		}
		return null;
	}


	private void putCategoryProcess() throws IOException, EntityPropertySizeLimitException{

		if(asString("accessible") != null){
			categoryModel.setAccessible(asBoolean("accessible"));
		}else{
			categoryModel.setContent(
					IOUtils.getStringFromInputStream(request.getInputStream()));
		}
		
		checkContentSize();
		putCategory();
	}

	
	private void deleteCategoryProcess(){
		Transaction tx = Datastore.beginTransaction();
		try {
			categoryService.delete(tx, categoryModel);
			tx.commit();
		} finally {
			if (tx.isActive()) {
				tx.rollback();
			}
		}
	}

	private void putCategoriesProcess() throws IOException, JsonParseException,
			JsonMappingException {
		
		ObjectMapper objectMapper = new ObjectMapper();
		FoldersBean folders = objectMapper.readValue(
				request.getInputStream(),
				FoldersBean.class);

		Transaction tx = Datastore.beginTransaction();

		try {
			// 現在のDBの情報を取得
			List<Folder> currentFoldersModel = categoryService
					.getCategoryFolders(userid);

			// ContentをDBの値よりセットする
			folders = setCategoryContentFromModelMap(
					folders,
					currentFoldersModel);

			// BeanからModelに変換
			Key userKey = Datastore.createKey(USER_META, userid);

			List<Folder> folderModels = new ArrayList<Folder>();
			List<Category> categoryModels = new ArrayList<Category>();

			for (FolderBean folder : folders.getFolders()) {
				Key folderKey = Datastore.createKey(
						userKey,
						FOLDER_META,
						folder.getId());
				Folder folderModel = new Folder();
				folderModel.setId(folderKey);
				folderModel.setOrder(folder.getOrder());
				folderModels.add(folderModel);

				categoryModels.addAll(createCategoryModelsFromBean(
						folder,
						userKey,
						folderModel));
			}

			categoryService.deleteCategoryFolders(tx, currentFoldersModel);
			categoryService.put(tx, folderModels);
			categoryService.put(tx, categoryModels);

			tx.commit();

		} finally {
			if (tx.isActive()) {
				tx.rollback();
			}
		}
	}

	private void getCategoriesProcess() throws IOException,
			JsonGenerationException, JsonMappingException {
		List<Folder> foldersModel = categoryService
				.getCategoryFolders(userid);
		
		if(!allCategoriesAccessible){
			foldersModel = ModelUtils.filterByAccessible(foldersModel);
		}
		
		FoldersBean foldersBean = getFoldersBeanFromModels(foldersModel);
		writeGetResponse(foldersBean);
	}

	private void getCategoryProcess() throws IOException {
		CategoryBean category = ModelUtils
				.createAndTransfer(categoryModel);
		if(!allCategoriesAccessible && !category.isAccessible()){
			response.setStatus(HttpServletResponse.SC_FORBIDDEN);
			return ;
		}
		
		response.setContentType("application/json; charset=utf-8");
		if(asString("jsonFileDownload") != null && asBoolean("jsonFileDownload")){
			String fileName = category.getName() + ".json";
			fileName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
			response.setHeader("Content-Disposition",
					 "attachment; filename=" + fileName);
		}
		PrintWriter out =  response.getWriter();
		out.println(category.getContent());
	}

	private List<Category> createCategoryModelsFromBean(FolderBean folder,
			Key userKey, Folder folderModel) {

		List<Category> categoryModels = new ArrayList<Category>();
		for (CategoryBean category : folder.getCategories()) {
			Key categoryKey = Datastore.createKey(
					userKey,
					CATEGORY_META,
					category.getId());

			Category categoryModel = new Category();
			categoryModel.setId(categoryKey);
			categoryModel.setName(category.getName());
			categoryModel.setContent(category.getContent());
			categoryModel.setAccessible(category.isAccessible());
			categoryModel.setOrder(category.getOrder());
			categoryModel.getFolderRef().setModel(folderModel);
			categoryModels.add(categoryModel);
		}
		return categoryModels;
	}

	private FoldersBean setCategoryContentFromModelMap(FoldersBean folders,
			List<Folder> currentFoldersModel) {

		// カテゴリをリストから外し全て取得
		Map<String, Category> categoryModelMap = transferCategoryModelsToMap(currentFoldersModel);

		for (FolderBean folder : folders.getFolders()) {
			for (CategoryBean category : folder.getCategories()) {
				Category categoryInDb = categoryModelMap.get(category.getId());
				if (categoryInDb != null) {
					//content, publishedに関してはDBの値と変わらず
					category.setContent(categoryInDb.getContent());
					category.setAccessible(categoryInDb.isAccessible());
				}
			}
		}
		return folders;
	}

	private Map<String, Category> transferCategoryModelsToMap(
			List<Folder> currentFoldersModel) {
		Map<String, Category> categoryModelMap = new HashMap<String, Category>();
		for (Folder f : currentFoldersModel) {
			for (Category categoryModel : f.getCategoriesRefs().getModelList()) {
				categoryModelMap.put(
						categoryModel.getId().getName(),
						categoryModel);
			}
		}
		return categoryModelMap;
	}

	private FoldersBean getFoldersBeanFromModels(List<Folder> foldersModel) {
		FoldersBean foldersBean = new FoldersBean();
		for (Folder folderModel : foldersModel) {
			foldersBean.add(ModelUtils.createAndTransfer(folderModel));
		}
		return foldersBean;
	}

	private <E> void writeGetResponse(E element) throws IOException,
			JsonGenerationException, JsonMappingException {

		response.setContentType("application/json; charset=utf-8");
		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.writeValue(response.getOutputStream(), element);
	}

}
